package com.tc.vms.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

public final class Utils {
    public static final int RMQ_HEART_BEAT_INTERVAL = 30; //in seconds
    public static final int MIN_DELAY_LEVEL = 1;
    public static final int MAX_DELAY_LEVEL = 16;
    public static final int VMS_MAX_TPS_LIMIT = 1024;
    public final static String PATH_JOIN_CHAR = "/";
    public final static String SHARDING_JOIN_CHAR = "#";
    public final static String SHARDING_INNER_JOIN_CHAR = "_";
    private final static Pattern SHARDING_PAT = Pattern.compile("\\d_\\d");
    public final static Random RANDOM = new Random(System.currentTimeMillis());
    public final static ThreadLocal<StringBuilder> LOCAL_STRING_BUILDER = new ThreadLocal<StringBuilder>() {
        @Override
        protected StringBuilder initialValue() {
            return new StringBuilder(64);
        }
    };

    public final static void sleep(long millseconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(millseconds);
        } catch (InterruptedException e) {
        }
    }

    public final static void sleep5s() {
        sleep(5 * 1000);
    }

    public final static void sleep3s() {
        sleep(3 * 1000);
    }


    public final static <T> T[] shuffle(T[] addrs) {
        List<T> list = new ArrayList<T>(Arrays.asList(addrs));
        Collections.shuffle(list);
        T[] result = (T[]) java.lang.reflect.Array.newInstance(addrs.getClass().getComponentType(), list.size());
        list.toArray(result);
        return result;
    }

    public static int getCpuCoreNum() {
        return Runtime.getRuntime().availableProcessors();
    }

    public static String randomStr() {
        return String.valueOf(Math.abs(RANDOM.nextInt()));
    }

    public static int randomInt() {
        return Math.abs(RANDOM.nextInt());
    }

    public static int randomInt(int maxValue) {
        return Math.abs(RANDOM.nextInt(maxValue));
    }

    public static List<String> split(String src, String sep) {
        ArrayList<String> rtn = new ArrayList<String>();
        if (src == null || src.isEmpty())
            return rtn;
        String[] strArray = src.split(sep);
        String tmpStr = null;
        for (String iStr : strArray) {
            tmpStr = iStr.trim();
            if (!iStr.trim().isEmpty()) {
                rtn.add(tmpStr);
            }
        }
        return rtn;
    }

    public static String abbrev(TimeUnit unit) {
        switch (unit) {
            case NANOSECONDS:
                return "ns";
            case MICROSECONDS:
                return "us";
            case MILLISECONDS:
                return "ms";
            case SECONDS:
                return "s";
            case MINUTES:
                return "m";
            case HOURS:
                return "h";
            case DAYS:
                return "d";
            default:
                throw new IllegalArgumentException("Unrecognized TimeUnit: " + unit);
        }
    }

    public static String getLocalAddress() {
        try {
            // Traversal Network interface to get the first non-loopback and non-private address
            Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
            ArrayList<String> ipv4Result = new ArrayList<String>();
            ArrayList<String> ipv6Result = new ArrayList<String>();
            while (enumeration.hasMoreElements()) {
                final NetworkInterface networkInterface = enumeration.nextElement();
                final Enumeration<InetAddress> en = networkInterface.getInetAddresses();
                while (en.hasMoreElements()) {
                    final InetAddress address = en.nextElement();
                    if (!address.isLoopbackAddress()) {
                        if (address instanceof Inet6Address) {
                            ipv6Result.add(normalizeHostAddress(address));
                        } else {
                            ipv4Result.add(normalizeHostAddress(address));
                        }
                    }
                }
            }

            // prefer ipv4
            if (!ipv4Result.isEmpty()) {
                for (String ip : ipv4Result) {
                    if (ip.startsWith("127.0") || ip.startsWith("192.168")) {
                        continue;
                    }

                    return ip;
                }

                return ipv4Result.get(ipv4Result.size() - 1);
            } else if (!ipv6Result.isEmpty()) {
                return ipv6Result.get(0);
            }
            //If failed to find,fall back to localhost
            final InetAddress localHost = InetAddress.getLocalHost();
            return normalizeHostAddress(localHost);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static String normalizeHostAddress(final InetAddress localHost) {
        if (localHost instanceof Inet6Address) {
            return "[" + localHost.getHostAddress() + "]";
        } else {
            return localHost.getHostAddress();
        }
    }

    public final static int getBrokerIdByShardingTag(String shardingTag) {
        String brokerId = split(shardingTag, SHARDING_INNER_JOIN_CHAR).get(0);
        try {
            return Integer.parseInt(brokerId);
        } catch (NumberFormatException e) {
            return brokerId.hashCode();
        }
    }

    public final static Set<String> splitShardsTagsByString(String src) {
        HashSet<String> rtn = new HashSet<String>(10);
        if (src == null || src.isEmpty())
            return rtn;
        try {
            Matcher matcher = SHARDING_PAT.matcher(src);
            while (matcher.find()) {
                rtn.add(matcher.group());
            }
        } catch (Exception e) {
        }
        return rtn;
    }

    public final static String joinShardsTags(String... src) {
        return mkString(SHARDING_JOIN_CHAR, src);
    }

    public final static String mkString(String join, String... parts) {
        int len = parts.length;
        if (len == 0)
            return "";
        if (len == 1)
            return parts[0];
        if (join == null) {
            join = "";
        }
        StringBuilder sb = LOCAL_STRING_BUILDER.get();
        sb.setLength(0);
        for (int i = 0; i < len; i++) {
            if (parts[i] == null || parts[i].isEmpty())
                continue;
            if (i == len - 1) {
                sb.append(parts[i]);
            } else {
                sb.append(parts[i]).append(join);
            }
        }
        return sb.toString();
    }

    public final static String mkZkPathString(String... parts) {
        return mkString(PATH_JOIN_CHAR, parts);
    }

    public static Properties parseProperties(String znodeData) throws IOException {
        Properties properties = new Properties();
        if (znodeData == null || znodeData.isEmpty())
            return properties;
        try {
            properties.load(new ByteArrayInputStream(znodeData.getBytes(ZkUtils.vmsCharEncoding)));
        } catch (IOException e) {
            throw e;
        }
        return properties;
    }

    public static Properties readByString(String text) {
        Properties properties = new Properties();
        String[] items = text.split("\n");
        for (String item : items) {
            int pos = item.indexOf("=");
            if (pos > -1) {
                String key = item.substring(0, pos);
                String value = trimEnd(item.substring(pos + 1), "\r");
                properties.put(key, value);
            }
        }
        return properties;
    }

    public static final Pattern PAT = Pattern.compile("[^a-zA-Z0-9\\._\\-]*");

    public static String normalize(String str) {
        return PAT.matcher(str).replaceAll("");
    }

    public static int getPid() {
        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
        String name = runtime.getName(); // format: "pid@hostname"
        try {
            return Integer.parseInt(name.substring(0, name.indexOf('@')));
        } catch (Exception e) {
            return -1;
        }
    }

    public static String getMXBeanName() {
        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
        return runtime.getName(); // format: "pid@hostname"
    }

    public static String getHostIp() {
        return getLocalAddress();
    }

    public static String getHostName() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

        return "DEFAULT_VMSCLIENT";
    }

    /**
     * convert byte array to hex string
     */
    public static String bytes2string(byte[] src) {
        StringBuilder sb = LOCAL_STRING_BUILDER.get();
        sb.setLength(0);
        if (src == null || src.length <= 0) {
            return null;
        }
        for (byte aSrc : src) {
            int v = aSrc & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                sb.append(0);
            }
            sb.append(hv.toUpperCase());
        }
        return sb.toString();
    }

    /**
     * convert hex string to byte array
     */
    public static byte[] string2bytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    private static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    public static byte[] uncompress(final byte[] src) throws IOException {
        byte[] result = src;
        byte[] uncompressData = new byte[src.length];
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(src);
        InflaterInputStream inflaterInputStream = new InflaterInputStream(byteArrayInputStream);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length);

        try {
            while (true) {
                int len = inflaterInputStream.read(uncompressData, 0, uncompressData.length);
                if (len <= 0) {
                    break;
                }
                byteArrayOutputStream.write(uncompressData, 0, len);
            }
            byteArrayOutputStream.flush();
            result = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw e;
        } finally {
            try {
                byteArrayInputStream.close();
            } catch (IOException e) {
            }
            try {
                inflaterInputStream.close();
            } catch (IOException e) {
            }
            try {
                byteArrayOutputStream.close();
            } catch (IOException e) {
            }
        }

        return result;
    }

    public static byte[] compress(final byte[] src, final int level) throws IOException {
        byte[] result = src;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length);
        java.util.zip.Deflater deflater = new java.util.zip.Deflater(level);
        DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
        try {
            deflaterOutputStream.write(src);
            deflaterOutputStream.finish();
            deflaterOutputStream.close();
            result = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            deflater.end();
            throw e;
        } finally {
            try {
                byteArrayOutputStream.close();
            } catch (IOException e) {
            }

            deflater.end();
        }

        return result;
    }

    public static int asInt(String str, int defaultValue) {
        try {
            return Integer.parseInt(str);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static long asLong(String str, long defaultValue) {
        try {
            return Long.parseLong(str);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static String formatDate(Date date, String pattern) {
        SimpleDateFormat df = new SimpleDateFormat(pattern);
        return df.format(date);
    }

    public static Date parseDate(String date, String pattern) {
        SimpleDateFormat df = new SimpleDateFormat(pattern);
        try {
            return df.parse(date);
        } catch (ParseException e) {
            return null;
        }
    }

    public static boolean numerical(String s) {

        if (s == null || s.trim().length() == 0) {
            return false;
        }
        char[] chars = s.toCharArray();
        int sz = chars.length;
        boolean hasExp = false;
        boolean hasDecPoint = false;
        boolean allowSigns = false;
        boolean foundDigit = false;
        // deal with any possible sign up front
        int start = (chars[0] == '-') ? 1 : 0;
        if (sz > start + 1) {
            if (chars[start] == '0' && chars[start + 1] == 'x') {
                int i = start + 2;
                if (i == sz) {
                    return false; // str == "0x"
                }
                // checking hex (it can't be anything else)
                for (; i < chars.length; i++) {
                    if ((chars[i] < '0' || chars[i] > '9') && (chars[i] < 'a' || chars[i] > 'f') && (chars[i] < 'A'
                            || chars[i] > 'F')) {
                        return false;
                    }
                }
                return true;
            }
        }
        sz--; // don't want to loop to the last char, check it afterwords
        // for type qualifiers
        int i = start;
        // loop to the next to last char or to the last char if we need another digit to
        // make a valid number (e.g. chars[0..5] = "1234E")
        while (i < sz || (i < sz + 1 && allowSigns && !foundDigit)) {
            if (chars[i] >= '0' && chars[i] <= '9') {
                foundDigit = true;
                allowSigns = false;

            } else if (chars[i] == '.') {
                if (hasDecPoint || hasExp) {
                    // two decimal points or dec in exponent
                    return false;
                }
                hasDecPoint = true;
            } else if (chars[i] == 'e' || chars[i] == 'E') {
                // we've already taken care of hex.
                if (hasExp) {
                    // two E's
                    return false;
                }
                if (!foundDigit) {
                    return false;
                }
                hasExp = true;
                allowSigns = true;
            } else if (chars[i] == '+' || chars[i] == '-') {
                if (!allowSigns) {
                    return false;
                }
                allowSigns = false;
                foundDigit = false; // we need a digit after the E
            } else {
                return false;
            }
            i++;
        }
        if (i < chars.length) {
            if (chars[i] >= '0' && chars[i] <= '9') {
                // no type qualifier, OK
                return true;
            }
            if (chars[i] == 'e' || chars[i] == 'E') {
                // can't have an E at the last byte
                return false;
            }
            if (chars[i] == '.') {
                if (hasDecPoint || hasExp) {
                    // two decimal points or dec in exponent
                    return false;
                }
                // single trailing decimal point after non-exponent is ok
                return foundDigit;
            }
            if (!allowSigns && (chars[i] == 'd' || chars[i] == 'D' || chars[i] == 'f' || chars[i] == 'F')) {
                return foundDigit;
            }
            if (chars[i] == 'l' || chars[i] == 'L') {
                // not allowing L with an exponent
                return foundDigit && !hasExp;
            }
            // last character is illegal
            return false;
        }
        return !allowSigns && foundDigit;
    }

    public static String combine(String joinStr, String... strsToCombine) {

        StringBuilder builder = LOCAL_STRING_BUILDER.get();
        builder.setLength(0);
        int idx = 0;

        for (String item : strsToCombine) {

            if (idx++ > 0 && !item.startsWith(joinStr)) {
                builder.append(joinStr);
            }

            if (item.endsWith(joinStr)) {
                builder.append(item.substring(0, item.length() - joinStr.length()));
            } else {
                builder.append(item);
            }
        }

        return builder.toString();
    }

    public static String trimEnd(String source, String s) {

        return source.endsWith(s) ? source.substring(0, source.length() - s.length()) : source;
    }

    public static String trimStart(String source, String s) {
        return source.startsWith(s) ? source.substring(s.length()) : source;
    }
}
